/*This controller class provides a list of selectoptions of all objects in the hosting Salesforce.com organization.
Upon the user selecting an object, entering some search text, and pressing the search button, the page will call the Search method which
will populate the Result class and return a list of Result records to be bound to the pageblocktable on the page. From there they can be acted
upon by the user.

The code uses SOSL to first find all the records which match the query text for the specified object, and it then loops through all of
the fields for that object to find and display the ones that contain the query text.

The replace code will take the value entered into the replaceword field and update the fields appropriately upon committing the Replace function.

Copyright (c) 2011 William Nourse

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

public with sharing class SearchandReplace {
public string queryword {get; set;}
public string replaceword {get; set;}
private Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
public List <Pair> fields {get; set;}
public List <SelectOption> objectNames {public get; private set;}
public String selectedObject {get; set;}
List<Result> ResultList {get; set;}
set<id> AccSet;

public list<sobject> accfull {get; set;}

//constructor
public SearchandReplace(){
    //get list of all objects in org
    objectNames = initObjNames();
        fields = new List<Pair>();
    
}
//Get list of Result objects which match queryword criteria. 
Public void Search(){
    
    ResultList = new List<Result>();
    getSearchResults(queryword);
    
}

Public void Replace(){
    getAccFull();
    
//Loop through all Result objects and if Selected=True, update the field with the replaceword value    
    for(Result res: ResultList){
        if(res.selected){
            for(sobject updateobj : AccFull){
                if(res.recid==updateobj.id){
                    system.debug('match');
                    string repstring=string.valueof(updateobj.get(res.field));
                    repstring = (repstring.toLowerCase()).trim();
                    repstring = repstring.replace(queryword, replaceword);
                    system.debug(repstring);
                    updateobj.put(res.field, repstring);
                }
            }
        }
        
    }
    update AccFull;
    search();
}

Public Void Clear(){
    queryword = '';
    replaceword='';
    ResultList.clear();
    accfull.clear();
    accset.clear();
    
}
Public List<Result> getResultList(){
    if(ResultList==null){
        ResultList = new List<Result>();
        
    
    }
    return ResultList;
    
}

Public List<sobject> getAccFull(){
    return accfull;
}
//Perform SOSL query on selected object for user input search term ('queryword').
//Then loop through all fields for the records returned to find any which contain the searchterm
//Finally, create a Result object for ID, Field Name and value and add to a list for display on the VF page
Public void getSearchResults(string qw){
    accset = new set<id>();
    qw = '\''+qw+'\'';
    String searchquery='FIND '+qw+' IN ALL FIELDS RETURNING '+selectedobject; 
    List<List<SObject>>searchList=search.query(searchquery);
    List<sObject> sobjectList = ((List<sobject>)searchList[0]);
    for(sObject sobj : sobjectlist){
        accset.add(sobj.id);
    }


    this.selectedObject=selectedobject;
    showfields();
    system.debug(fields.size());
    string fieldlist=' ';
    integer r = 0;

    for(Pair field : fields){
        r=r+1;
        system.debug(r);
        if(r<fields.size()){
            fieldlist = fieldlist+field.key+', ';
        }
        else{fieldlist = fieldlist+field.key;
        }
    }

    string querytext;
    //having issues with different treatments Name fields on Person Account related objects (Account and Contact).
    //So modify query depending on Object.  This could be problematic in orgs that don't have Person Accounts enabled.  Need to check in Dev Org
    if(selectedObject!='Account' && selectedObject!='Contact'){
        querytext ='select'+fieldlist+' from ' + selectedobject+ ' where id in : accset';
    }
    else{
        querytext ='select'+fieldlist+' from ' + selectedobject+ ' where ispersonaccount=False and id in : accset';
    }

    accfull = database.query(querytext);

    for(sobject acc : Accfull){
    
        for(pair field2 : fields){
        
            object fieldval = acc.get(field2.key);
            string fv =string.valueof(fieldval);
        
            system.debug(fv);
            if(fv!=null){
                //need to convert field values to lower case and trim in order to match properly
                fv=fv.toLowerCase();
                fv=fv.trim();
                system.debug(fv);
                system.debug(qw);
                qw=qw.trim();
                boolean result = fv.contains(queryword);
                if(result==true){
                    system.debug('keyword found');
                    Result resdata = new Result();
                    resdata.obj =  string.valueof(acc.get('name'));
                    resdata.recid = string.valueof(acc.get('id'));
                    resdata.field = field2.key;
                    resdata.value = fv;
                    ResultList.add(resdata);
            
                }
            }
        }
    }
}

 
 
// Populate SelectOption list  -   only with objects which are searchable in the user context
     
    
    private List<SelectOption> initObjNames() {
        List<SelectOption> objNames = 
            new List<SelectOption>();
        List<String> entities = 
            new List<String>(schemaMap.keySet());
        entities.sort();
        for(String name : entities){
            
                Schema.sObjectType sobject_type = schemamap.get(name);
                Schema.DescribeSObjectResult sobject_describe = sobject_type.getDescribe(); 
                boolean Searchable = sobject_describe.issearchable();
                if(Searchable){
                objNames.add(new SelectOption(name,name));
                }
        }
        return objNames;
    }
    
    // Find the fields for the selected object  
    
    public void showFields() {
        fields.clear();
        Map <String, Schema.SObjectField> fieldMap = 
        schemaMap.get(selectedObject).getDescribe().fields.getMap();
    
        for(Schema.SObjectField sfield : fieldMap.Values()){
            schema.describefieldresult dfield = 
                sfield.getDescribe();      
          //only get fields with type String or ID - may need to modify to only get Updateable as well     
          if((dfield.gettype()==DisplayType.string && dfield.isupdateable())|| dfield.gettype()==DisplayType.id){
            Pair field = new Pair();      
            field.key = dfield.getname();
            fields.add(field);
          }
        }
    }

    //class to handle field names and values for the selected object
    public class Pair {
        public String key {get; set;}
        public String val {get; set;}       
    }
    //class to contain results of the search 
    public class Result {
        public string obj {get; set;}
        public string recid {get; set;}
        public string field {get; set;}
        public  string value {get; set;}
        public boolean selected {get; set;}
        //constructor
        public Result(){
            //selected value set to false to require user to choose which records/fields to update
            selected= false;
            
        }
    }
    
    static testMethod void test() {
        testSoslFixedResults();
        SearchandReplace SAR = new SearchandReplace();
        system.assert(SAR.objectnames.size()>0);
        SAR.queryword = 'testaccount';
        system.assertequals('testaccount', SAR.queryword);  
        SAR.selectedObject = 'Account';
        SAR.Search();
        system.assert(SAR.ResultList.size()>0);
        SAR.replaceword = 'toast';
        system.assertequals('toast', SAR.replaceword);
        system.assertequals(SAR.ResultList.size(), 1);
        SAR.ResultList[0].selected=true;
        
        SAR.Replace();
        account resultaccount = [select id from account where name = 'toast'];
        system.assert(resultaccount!=null);
    }
    
    
//required method to test SOSL queries
    public static testMethod void testSoslFixedResults() {
        Account testacc = new account(name ='testaccount');
        insert testacc;
       Id [] fixedSearchResults= new Id[1];
       fixedSearchResults[0] = testacc.id;
       Test.setFixedSearchResults(fixedSearchResults);
       List<List<SObject>> searchList = [FIND 'testaccount' IN ALL FIELDS RETURNING Account(id, name WHERE name = 'testaccount' LIMIT 1)];
    }
   

}